import os
import re
import sys
from pathlib import Path

import paramiko
from flask import make_response, request, send_from_directory
from flask_restx import abort

from app.models import Courseware
from app.role_control import UploadFileRuleControl, DeleteFileRuleControl, DownloadFileRuleControl, \
    NewUploadFileRuleControl, NewDownloadFileRuleControl
from configs.settings import flask_config
from tools import db
from utils.crypto import decrypt_pw
from utils.utils import create_md5

from .urls import loadBlueprint, loadBlueprint_par

UPLOAD_FOLDER = flask_config.UPLOAD_FOLDER
ALLOWED_EXTENSIONS = {"ppt", "pptx", "pdf", "mp4", "doc", "docx", "wmv", "avi", "zip"}


def allowed_file(filename):
    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS


def file_suffix(filename):
    return "." + filename.rsplit(".", 1)[1].lower()


def secure_filename(filename):
    text_type = str
    _windows_device_files = (
        "CON",
        "AUX",
        "COM1",
        "COM2",
        "COM3",
        "COM4",
        "LPT1",
        "LPT2",
        "LPT3",
        "PRN",
        "NUL",
    )
    if isinstance(filename, text_type):
        from unicodedata import normalize

        filename = normalize("NFKD", filename).encode("utf-8", "ignore")
        PY2 = sys.version_info[0] == 2
        if not PY2:
            filename = filename.decode("utf-8")
    for sep in os.path.sep, os.path.altsep:
        if sep:
            filename = filename.replace(sep, " ")
    _filename_ascii_add_strip_re = re.compile(r"[^A-Za-z0-9_\u4E00-\u9FBF.-]")
    filename = str(
        _filename_ascii_add_strip_re.sub("", "_".join(filename.split()))
    ).strip("._")

    if (
        os.name == "nt"
        and filename
        and filename.split(".")[0].upper() in _windows_device_files
    ):
        filename = f"_{filename}"

    return filename


@loadBlueprint.route("/upload", methods=["POST"], role_control=UploadFileRuleControl)
def upload_file():
    if "file" not in request.files:
        abort(make_response("No file part", 400))

    try:
        file = request.files["file"]
        if file and allowed_file(file.filename):
            # suffix = file_suffix(file.filename)
            filename = create_md5(file.filename)
            abspath = os.path.join(os.path.dirname(os.path.abspath(__file__)))
            file_path = Path(abspath, "path")
            if not os.path.exists(file_path):
                os.makedirs(file_path, exist_ok=True)
            file.save(Path(abspath, "path", filename))
            # file.save(Path(abspath, 'path', filename+suffix))
            # return {'code': 200, 'data': {"filename": filename+suffix}}
            return {"code": 200, "data": {"filename": filename, "realFilename": file.filename}}

    except Exception as e:
        abort(make_response(str(e), 400))


@loadBlueprint.route("/delete_file", methods=["POST"], role_control=DeleteFileRuleControl)
def delete_file():
    params = request.json
    filename = params.get("filename", "")
    file_type = params.get("file_type", "")
    name = params.get("name", "")
    if not all([filename, file_type]):
        abort(make_response("参数错误", 400))

    abspath = os.path.join(os.path.dirname(os.path.abspath(__file__)))
    file_path = Path(abspath, "path", filename)
    if not os.path.exists(file_path):
        abort(make_response("FileNotFoundError", 400))
    if name:
        courseware_info = Courseware.query.filter_by(name=name).first()
        if not courseware_info:
            abort(400, message="该课件不存在")

    try:
        os.remove(file_path)
        Courseware.query.filter_by(name=name).update({file_type: None})
        db.session.commit()
        return {"code": 200, "data": f"删除{filename}成功"}

    except Exception as e:
        db.session().rollback()
        abort(make_response(str(e), 400))


@loadBlueprint_par.route("/download", methods=["POST"], role_control=DownloadFileRuleControl)
@loadBlueprint.route("/download", methods=["POST"], role_control=DownloadFileRuleControl)
def download_file():
    filename = request.json.get("filename", "")
    if not filename or not isinstance(filename, str) or len(filename.strip()) == 0:
        abort(make_response("请上传正确文件名", 400))

    abspath = os.path.join(os.path.dirname(os.path.abspath(__file__)))
    upload_path = Path(abspath, "path")
    try:
        if Path(upload_path, filename).exists():
            return send_from_directory(upload_path, filename)
        abort(make_response(f"<{filename}>不存在", 400))

    except Exception as e:
        abort(make_response(str(e), 400))


@loadBlueprint.route("/new_upload", methods=["POST"], role_control=NewUploadFileRuleControl)
def new_upload_file():
    try:
        if "file" not in request.files:
            abort(400, message="No file part")

        file = request.files["file"]
        if file and allowed_file(file.filename):
            filename = create_md5(file.filename)
            storage = decrypt_pw(flask_config.STORAGE)
            transport = paramiko.Transport(storage["HOSTNAME"], storage["PORT"])
            transport.connect(
                username=storage["USERNAME"], password=storage["password"]
            )
            sftp = paramiko.SFTPClient.from_transport(transport)
            remote_dir = storage["PATH"] + filename
            sftp.putfo(file.stream, remote_dir, confirm=True)
            transport.close()
            return {"code": 200, "data": {"filename": filename}}

    except Exception as e:
        abort(400, message=str(e))


@loadBlueprint.route("/new_download/<filename>", methods=["GET"], role_control=NewDownloadFileRuleControl)
def new_download_file(filename):
    try:
        if not filename or not isinstance(filename, str) or len(filename.strip()) == 0:
            abort(400, message="请上传正确文件名")

        storage = flask_config.STORAGE
        transport = paramiko.Transport(storage["HOSTNAME"], storage["PORT"])
        transport.connect(username=storage["USERNAME"], password=storage["PW"])
        sftp = paramiko.SFTPClient.from_transport(transport)
        remote_path = storage["PATH"] + filename
        abspath = os.path.join(os.path.dirname(os.path.abspath(__file__)))
        upload_path = Path(abspath, "test")
        if not os.path.exists(upload_path):
            os.makedirs(upload_path, exist_ok=True)
        sftp.get(remote_path, Path(abspath, "test", filename))
        if Path(upload_path, filename).exists():
            return send_from_directory(upload_path, filename)
        else:
            abort(400, message=f"<{filename}>不存在")

    except Exception as e:
        abort(400, message=str(e))
